package net.virtuemed.jt808.vo;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.util.ReferenceCountUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import net.virtuemed.jt808.config.JT808Const;
import net.virtuemed.jt808.util.BCDUtil;
import net.virtuemed.jt808.util.Bit;
import net.virtuemed.jt808.util.JT808Util;
import org.apache.commons.lang3.StringUtils;

/**
 * 解析消息头和消息体
 *
 * @Description DataPacket
 * @Author guoyb
 * @Date 2023/10/13 15:48
 */
@Data
@Slf4j
public class DataPacket {

    protected Header header = new Header(); //消息头
    protected ByteBuf payload; //消息体

    public DataPacket() {
    }

    public DataPacket(ByteBuf payload) {
        this.payload = payload;
    }

    public void parse() {
        try {
            this.parseHead();

            //判断消息体长度
            if (this.header.getMsgBodyLength() != this.payload.readableBytes()) {
                throw new RuntimeException("包体长度有误");
            }
            this.parseBody();
        } finally {
            ReferenceCountUtil.safeRelease(this.payload);
        }
    }

    /**
     * 解析头部
     */
    protected void parseHead() {

        // ByteBuf.readShort()是Netty中的一个方法，用于从ByteBuf中读取一个短整型（2个字节），并将当前读索引向前移动2个字节
        header.setMsgId(payload.readShort());
        header.setMsgBodyProps(payload.readShort());

        if (header.hasSubPackage()) {
            log.info("处理分包-----打印日志");
            // // 如果有分包则多读4个字节
            // packTotal = Integer.parseInt(reader.readByte(2), 16);
            // readInt() 方法会从 ByteBuf 中读取四个字节，并将其解释为整型值,ByteBuf 的读索引会相应地向前移动四个字节。

            //如果想在指定位置读取整型值，而不是使用当前的读索引位置，可以使用 getInt(int index) 方法,getInt() 方法不会改变读索引的位置。
            payload.readInt();
        }

        int version = 0;//14位为0标识2011版本(2013),1:代表引入了版本
        short msgBodyProps = header.getMsgBodyProps();
        if (Bit.isTrue(msgBodyProps, 14))//识别2019及后续版本
        {
            version = payload.readByte();
        }
        header.setProtocolVersion(version);

        // String terminalPhone = version > 0 ? readBytes(10) : readBytes(6);
        if (version==0) {
            // 2011,2013版中终端手机号长度为6字节，2019版中终端手机号长度为10字节。
            // 如果版本号大于0 则手机号为10位
            header.setTerminalPhone(BCDUtil.BCDtoString(readBytes(6)));
        }else{
            log.info("2019版本----");
            header.setTerminalPhone(BCDUtil.bcdToString(readBytes(10)));
        }

        //流水号
        header.setFlowId(payload.readShort());

    }

    /**
     * 请求报文重写-需要子类重写
     */
    protected void parseBody() {

    }

    /**
     * 响应报文重写 并调用父类
     *
     * @return
     */
    public ByteBuf toByteBufMsg() {
        ByteBuf bb = ByteBufAllocator.DEFAULT.heapBuffer();//在JT808Encoder escape()方法处回收
        bb.writeInt(0);//先占4字节用来写msgId和msgBodyProps，JT808Encoder中覆盖回来

        //StringUtils.leftPad:用于在字符串的左侧填充指定的字符，使字符串达到指定的长度
        bb.writeBytes(BCDUtil.toBcdBytes(StringUtils.leftPad(this.header.getTerminalPhone(), 12, "0")));
        bb.writeShort(this.header.getFlowId());
        //TODO 处理分包
        return bb;
    }

    /**
     * 从ByteBuf中read固定长度的数组,相当于ByteBuf.readBytes(byte[] dst)的简单封装
     *
     * @param length
     * @return
     */
    public byte[] readBytes(int length) {
        byte[] bytes = new byte[length];
        this.payload.readBytes(bytes);
        return bytes;
    }

    /**
     * 从ByteBuf中读出固定长度的数组 ，根据808默认字符集构建字符串
     *
     * @param length
     * @return
     */
    public String readString(int length) {
        return new String(readBytes(length), JT808Const.DEFAULT_CHARSET);
    }

    //通过使用前缀0b，可以直接在代码中表示二进制数，
    //第十四位是否是1
    private static final int VERSION = 0b0100_0000_0000_0000;

    //同: 0x3ff
    private static final int BODY_LENGTH = 0b0000_0011_1111_1111;

    @Data
    public static class Header {
        private short msgId;// 消息ID 2字节

        // byte[2-3]
        private short msgBodyProps;//消息体属性

        private int protocolVersion;//协议版本

        private String terminalPhone; // 终端手机号:6字节(2019版本:10个字节)

        private short flowId;// 流水号 2字节

        //获取消息体长度
        public short getMsgBodyLength() {
            //结果将是 msgBodyProps 的后 10 位的值。
            return (short) (msgBodyProps & 0x3ff);
        }

        /**
         * 获取加密类型 3bits -0不加密,1:RSA加密
         * @return
         */
        public byte getEncryptionType() {
            return (byte) ((msgBodyProps & 0x1c00) >> 10);
        }

        //是否分包,1:分包 , 0:不分包,消息头里无消息包封顶项字段
        //取第13位是否为1 , 0x2000二进制是0010000000000000
        public boolean hasSubPackage() {
            // boolean isPack = (msgBodyInt & 0x2000) > 0;
            return ((msgBodyProps & 0x2000) >> 13) == 1;
        }

        /**
         * 是否有版本,
         * 0: 2011年版本(或者是2013),
         * 1: 引入版本 ,会跟随协议版本号,初始为1(2019版本),2019版本开始从1递增
         */
        public boolean isVersion() {
            return (msgBodyProps & VERSION) == VERSION;
        }

    }

}
